#include <windows.h>
#include "Win32CacheEntry.h"
#include "Win32CacheManager.h"
#include "../../LibUtil/LibUtil.h"
USING_LIB_UTIL;

#ifndef SAFE_DELETE
#define SAFE_DELETE(o) if(o) delete (o); (o) = NULL
#endif

#ifndef SAFE_DELETE_ARRAY
#define SAFE_DELETE_ARRAY(o) if(o) delete[] (o); (o) = NULL
#endif 

LIB_CACHE_NAMESPACE_BEGIN

Win32CacheManager::Win32CacheManager(void)
{
	m_checkHash = true;
	m_maxMemSize = 0;
	m_maxFolderSize = 0;
	m_maxEntrySize = 0;
	m_currentCacheSize = 0;
	m_currentFolderSize = 0;
	m_rwlock = new pthread_rwlock_t;
	pthread_rwlock_init(m_rwlock, NULL);
}

Win32CacheManager::~Win32CacheManager(void)
{
	ReleaseEntry();
	pthread_rwlock_destroy(m_rwlock);
	SAFE_DELETE(m_rwlock);
}

bool Win32CacheManager::Initialize(const char* cacheBaseDir,
									bool checkHash, 
									const size_t& maxMemSize,
									const size_t& maxFolderSize,
									const size_t& maxCacheEntrySize)
{
	if(cacheBaseDir == NULL)
	{
		char path[1024];
		_fullpath(path, 0, 1024);
		m_baseDir = path;
	}
	else
		m_baseDir = cacheBaseDir;
	if(m_baseDir.at(m_baseDir.length() - 1) != '\\')
		m_baseDir += "\\";

	HANDLE hFile = CreateFile(m_baseDir.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
								OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
	DWORD dw = GetLastError();
	if(INVALID_HANDLE_VALUE == hFile && !CreateDirectory(m_baseDir.c_str(), NULL))
		return false;

	m_checkHash = checkHash;
	m_maxMemSize = maxMemSize;
	m_maxFolderSize = maxFolderSize;
	m_maxEntrySize = maxCacheEntrySize;

	LoadEntry();
	return true;
}

bool Win32CacheManager::Destroy()
{
	delete this;
	return true;
}

bool Win32CacheManager::Refresh()
{
	ReleaseEntry();
	return LoadEntry();
}

const char* Win32CacheManager::GetCacheBaseDir() const
{
	return m_baseDir.c_str();
}

bool Win32CacheManager::GetCache(const char* name, 
								  ICacheEntry** cacheEntry, 
								  bool checkDisk, 
								  bool checkHash) const
{
	pthread_rwlock_rdlock(m_rwlock);
	tCacheMap::const_iterator iter = m_cacheMap.find(name);
	if(iter != m_cacheMap.end())
	{
		if(cacheEntry != NULL)
			*cacheEntry = iter->second;
		pthread_rwlock_unlock(m_rwlock);
		return true;
	}
	pthread_rwlock_unlock(m_rwlock);
	if(!checkDisk)
		return false;
	
	ICacheEntry* entry = const_cast<Win32CacheManager*>(this)->AddCacheEntry(name, checkHash);
	if(cacheEntry != NULL)
		*cacheEntry = entry;
	bool ret = entry != NULL;
	return ret;
}

bool Win32CacheManager::AddCache(const char* name, 
								  const ICacheEntry* cacheEntry, 
								  bool writeToDisk,
								  bool checkHash)
{
	if(cacheEntry == NULL || cacheEntry->IsDirty())
		return false;

	cacheEntry->Lock();
	tDataBuffer buffer(true);
	if(!cacheEntry->GetRawBuffer(&buffer.data, buffer.size))
	{
		cacheEntry->UnLock();
		return false;
	}

	if(checkHash && !MD5HashHelper::CheckDataBufferHash(buffer, name))
	{
		cacheEntry->UnLock();
		return false;
	}
	
	pthread_rwlock_wrlock(m_rwlock);
	m_cacheMap[name] = const_cast<ICacheEntry*>(cacheEntry);
	pthread_rwlock_unlock(m_rwlock);
	cacheEntry->UnLock();

	if(!writeToDisk)
		return true;
	else
		return cacheEntry->PersistData();
}
bool Win32CacheManager::AddCache(const char* name, 
								  const byte* data, 
								  const size_t size,
								  ICacheEntry** cacheEntry,
								  bool writeToDisk,
								  bool checkHash)
{
	ICacheEntry* entry = NULL;
	CreateCacheEntry(name, &entry);
	size_t writeSize = 0;
	if(!entry->Write(data, size, writeSize))
		return false;

	bool ret = AddCache(name, entry, writeToDisk, checkHash);
	if(cacheEntry == NULL)
		return ret;
	if(ret)
		*cacheEntry = entry;
	else
		*cacheEntry = NULL;
	return ret;
}

bool Win32CacheManager::RemoveCache(const char* name, bool removeDiskCopy)
{
	pthread_rwlock_wrlock(m_rwlock);
	tCacheMap::iterator iter = m_cacheMap.find(name);
	if(iter == m_cacheMap.end())
	{
		pthread_rwlock_unlock(m_rwlock);
		return false;
	}
	m_cacheMap.erase(iter);
	pthread_rwlock_unlock(m_rwlock);

	if(!removeDiskCopy)
		return true;

	string path = m_baseDir + name;
	return DeleteFile(path.c_str());
}

bool Win32CacheManager::CreateCacheEntry(const char* name, ICacheEntry** cacheEntry) const
{
	if(cacheEntry == NULL)
		return false;
	*cacheEntry = new Win32CacheEntry(m_baseDir);
	(*cacheEntry)->SetName(name);
	return true;
}

bool Win32CacheManager::ReleaseCacheEntry(ICacheEntry* cacheEntry) const
{
	if(cacheEntry == NULL)
		return false;
	SAFE_DELETE(cacheEntry);
	return true;
}

bool Win32CacheManager::Lock(bool read) const
{
	if(read)
		return pthread_rwlock_rdlock(m_rwlock);
	else
		return pthread_rwlock_wrlock(m_rwlock) == 0;
}

bool Win32CacheManager::UnLock() const
{
	return pthread_rwlock_unlock(m_rwlock) == 0;
}

bool Win32CacheManager::TryLock(bool read) const
{
	if(read)
		return pthread_rwlock_tryrdlock(m_rwlock);
	else
		return pthread_rwlock_trywrlock(m_rwlock);
}

void Win32CacheManager::ReleaseEntry()
{
	pthread_rwlock_wrlock(m_rwlock);
	tCacheMap::iterator iter = m_cacheMap.begin();
	for(; iter != m_cacheMap.end(); iter++)
	{
		ICacheEntry* entry = iter->second;
		SAFE_DELETE(entry);
	}
	m_cacheMap.clear();
	pthread_rwlock_unlock(m_rwlock);
}

bool Win32CacheManager::LoadEntry()
{
	WIN32_FIND_DATA FindFileData;
	HANDLE hFind;

	string searchPath = m_baseDir;
	string rootPath(searchPath);
	searchPath += "*.*";

	hFind = FindFirstFile(searchPath.c_str(), &FindFileData);
	if(hFind == INVALID_HANDLE_VALUE) 
		return false;

	while(FindNextFile(hFind, &FindFileData))
	{
		if(m_currentCacheSize > m_maxMemSize)
			break;

		if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			continue;

		if(m_cacheMap.find(FindFileData.cFileName) != m_cacheMap.end())
			continue;

		
		AddCacheEntry(FindFileData.cFileName, m_checkHash);
	}
	FindClose(hFind);
	return true;
}

ICacheEntry* Win32CacheManager::AddCacheEntry(const string& name, bool checkHash)
{
	pthread_rwlock_wrlock(m_rwlock);
	if(m_cacheMap.find(name) != m_cacheMap.end())
	{
		pthread_rwlock_unlock(m_rwlock);
		return NULL;
	}

	ICacheEntry* cacheEntry = NULL;
	CreateCacheEntry(name.c_str(), &cacheEntry);
	cacheEntry->LoadPersistData();
	tDataBuffer buffer(true);
	cacheEntry->GetRawBuffer(&buffer.data, buffer.size);
	if(checkHash && !MD5HashHelper::CheckDataBufferHash(buffer, name.c_str()))
	{
		SAFE_DELETE(cacheEntry);
		pthread_rwlock_unlock(m_rwlock);
		return NULL;
	}

	m_cacheMap[name] = cacheEntry;
	m_currentCacheSize += buffer.size;
	pthread_rwlock_unlock(m_rwlock);
	return cacheEntry;
}

ICacheManager* ICacheManager::CreateCacheManager()
{
	return new Win32CacheManager;
}

LIB_CACHE_NAMESPACE_END
